// $Id: Persistence.cs 10 2008-02-29 15:58:02Z mcartoixa $
/*
 *  Copyright (C) 2007 NourY Solutions
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */
/*! \file
 * \brief Generic interfaces of persistent objects.
 *
 * \author $Author: mcartoixa $
 * \date $Date: 2008-02-29 16:58:02 +0100 (ven., 29 févr. 2008) $
 * \version $Revision: 10 $
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;

namespace Salamanca.DataAccess
{



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Interface implemented by a persistence context.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public interface IPersistenceContext:
        IDisposable
    {
        /// <summary>Indicates whether the persistence action is forced.</summary>
        bool ForceAction
        {
            get;
        }

        /// <summary>The <see cref="ITransaction">transaction</see> in which the persistence action is executed.</summary>
        ITransaction Transaction
        {
            get;
        }

        /// <summary>Commits the operations executed in this <see cref="IPersistenceContext" />.</summary>
        void Commit();
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Implementation of a persistence context.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public sealed class PersistenceContext:
        IPersistenceContext
    {

        /// <summary>Constructor.</summary>
        public PersistenceContext():
            this(false)
        {
        }

        /// <summary>Constructor.</summary>
        public PersistenceContext(bool forceAction):
            this(forceAction, new Transaction())
        {
            // We own the transaction
            _DisposeTransaction=true;
        }

        /// <summary>Constructor.</summary>
        public PersistenceContext(bool forceAction, ITransaction transaction)
        {
            Debug.Assert(transaction!=null);
            if (transaction==null)
                throw new ArgumentNullException("transaction");

            _ForceAction=forceAction;
            _Transaction=transaction;
        }

        /// <summary>Finalizer.</summary>
        ~PersistenceContext()
        {
            Dispose(false);
        }

        /// <summary>Commits the operations executed in this <see cref="PersistenceContext" />.</summary>
        /// <remarks>This method does nothing more than <see cref="ITransaction.Commit" /> the current <see cref="Transaction" />.</remarks>
        public void Commit()
        {
            _Transaction.Commit();
        }

        /// <summary>Frees the resources of this instance.</summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>Frees the resources of this instance.</summary>
        private void Dispose(bool disposing)
        {
            if (!_Disposed)
            {
                if (_DisposeTransaction)
                    // We own the transaction
                    Transaction.Dispose();

                _Disposed=true;
            }
        }

        /// <summary>Indicates whether the persistence action is forced.</summary>
        public bool ForceAction
        {
            get
            {
                return _ForceAction;
            }
        }

        /// <summary>The <see cref="ITransaction">transaction</see> in which the persistence action is executed.</summary>
        public ITransaction Transaction
        {
            get
            {
                return _Transaction;
            }
        }

        private bool _ForceAction;
        private ITransaction _Transaction;
        private bool _Disposed;
        private bool _DisposeTransaction;
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Interface implemented by a persistence context provider.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public interface IPersistenceContextProvider
    {

        /// <summary>Creates a persistence context.</summary>
        IPersistenceContext CreatePersistenceContext();

        /// <summary>Creates a persistence context.</summary>
        IPersistenceContext CreatePersistenceContext(bool forceAction);
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Default implementation of a persistence context provider.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public sealed class PersistenceContextProvider:
        IPersistenceContextProvider
    {

        /// <summary>Creates a persistence context.</summary>
        public IPersistenceContext CreatePersistenceContext()
        {
            return new PersistenceContext();
        }

        /// <summary>Creates a persistence context.</summary>
        public IPersistenceContext CreatePersistenceContext(bool forceAction)
        {
            return new PersistenceContext(forceAction);
        }
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Arguments for a persistence event.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public class PersistenceEventArgs:
        EventArgs
    {

        /// <summary>Constructor.</summary>
        public PersistenceEventArgs(IPersistenceContext context)
        {
            _Context=context;
        }

        /// <summary>The persistence context.</summary>
        public IPersistenceContext Context
        {
            get
            {
                return _Context;
            }
        }

        /// <summary>The persistence context.</summary>
        private IPersistenceContext _Context;
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Interface implemented by a persistent object.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public interface IPersistent
    {

        /// <summary>Deletes the internal data of the instance in the specified persistence context.</summary>
        void Delete(IPersistenceContext context);
        /// <summary>Saves the internal data of the instance in the specified persistence context.</summary>
        void Save(IPersistenceContext context);


        /// <summary>Occurs when the instance is about to be deleted (<see cref="Delete(IPersistenceContext)" />).</summary>
        event EventHandler<PersistenceEventArgs> Deleting;
        /// <summary>Occurs when the instance has been deleted (<see cref="Delete(IPersistenceContext)" />).</summary>
        event EventHandler<PersistenceEventArgs> Deleted;
        /// <summary>Occurs when the instance is about to be saved (<see cref="Save(IPersistenceContext)" />).</summary>
        event EventHandler<PersistenceEventArgs> Saving;
        /// <summary>Occurs when the instance has been saved (<see cref="Save(IPersistenceContext)" />).</summary>
        event EventHandler<PersistenceEventArgs> Saved;
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Attribute used to associate a persistent specific value to an enumerated value.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    [AttributeUsage(AttributeTargets.Field, Inherited=false)]
    public sealed class PersistentValueAttribute:
        Attribute
    {
        /// <summary>Initializes a new instance of the <see cref="PersistentValueAttribute"/> class.</summary>
        public PersistentValueAttribute(string value)
        {
            _Value=value;
        }

        /// <summary>The persistent value associated to the enumerated value.</summary>
        public string Value
        {
            get
            {
                return _Value;
            }
        }

        /// <summary>Indicates whether this value is the default, if no value is specified.</summary>
        public bool IsDefault
        {
            get
            {
                return _IsDefault;
            }
            set
            {
                _IsDefault=value;
            }
        }

        private string _Value;
        private bool _IsDefault;
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Formatter used to transform an enumerated value into its persistent representation.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public class PersistentFormatter:
        ICustomFormatter,
        IFormatProvider
    {
        /// <summary>Constructor.</summary>
        public PersistentFormatter()
        {
        }

        /// <summary>Gets an object that defines how to format the specified type.</summary>
        public object GetFormat(Type formatType)
        {
            if (formatType==typeof(ICustomFormatter))
                return this;

            return null;
        }

        /// <summary>Converts the value of a specified object to an equivalent string representation using specified format and culture-specific formatting information.</summary>
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if ((arg!=null) && (arg is Enum) && ((format==null) || (format=="G")))
            {
                PersistentValueAttribute[] pva=(PersistentValueAttribute[])(arg.GetType().GetField(arg.ToString())).GetCustomAttributes(typeof(PersistentValueAttribute), false);
                foreach (PersistentValueAttribute va in pva)
                    return va.Value;
            }

            if (arg is IFormattable)
                return ((IFormattable)arg).ToString(format, formatProvider);
            else if (arg!=null)
                return arg.ToString();

            return string.Empty;
        }
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Provides a type converter to convert <see cref="Enum" /> objects to and from various other representations.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public class PersistentEnumConverter:
        EnumConverter
    {

        /// <summary>Initializes a new instance of the <see cref="PersistentEnumConverter"></see> class for the given type.</summary>
        /// <param name="type">A <see cref="T:System.Type"></see> that represents the type of enumeration to associate with this enumeration converter. </param>
        public PersistentEnumConverter(Type type):
            base(type)
        {
        }

        /// <summary>Converts the specified value object to an enumeration object.</summary>
        /// <returns>An <see cref="Object" /> that represents the converted value.</returns>
        /// <param name="culture">An optional <see cref="CultureInfo" />. If not supplied, the current culture is assumed.</param>
        /// <param name="context">An <see cref="ITypeDescriptorContext" /> that provides a format context.</param>
        /// <param name="value">The <see cref="Object" /> to convert.</param>
        /// <exception cref="NotSupportedException">The conversion cannot be performed.</exception>
        /// <exception cref="FormatException"><paramref name="value" /> is not a valid value for the target type.</exception>
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if ((value==null) || (value.GetType()==typeof(string)))
            {
                foreach (FieldInfo fi in EnumType.GetFields())
                {
                    PersistentValueAttribute[] pva=(PersistentValueAttribute[])fi.GetCustomAttributes(typeof(PersistentValueAttribute), true);
                    foreach (PersistentValueAttribute va in pva)
                    {
                        if (((value==null) && (va.Value==null)) || (string.Compare(va.Value, (string)value)==0))
                            return fi.GetValue(null);
                    }
                }
            }

            foreach (FieldInfo fi in EnumType.GetFields())
            {
                PersistentValueAttribute[] pva=(PersistentValueAttribute[])fi.GetCustomAttributes(typeof(PersistentValueAttribute), true);
                foreach (PersistentValueAttribute va in pva)
                    if (va.IsDefault)
                        return fi.GetValue(null);
            }

            throw new NotSupportedException();
        }

        /// <summary>Converts the given value object to the specified destination type.</summary>
        /// <returns>An <see cref="Object" /> that represents the converted value.</returns>
        /// <param name="culture">An optional <see cref="CultureInfo" />. If not supplied, the current culture is assumed.</param>
        /// <param name="context">An <see cref="ITypeDescriptorContext" /> that provides a format context.</param>
        /// <param name="destinationType">The <see cref="Type" /> to convert the value to.</param>
        /// <param name="value">The <see cref="Object" /> to convert.</param>
        /// <exception cref="ArgumentException">value is not a valid value for the enumeration.</exception>
        /// <exception cref="ArgumentNullException">destinationType is null.</exception>
        /// <exception cref="NotSupportedException">The conversion cannot be performed.</exception>
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if ((destinationType==typeof(string)) && ((culture==null) || (culture==CultureInfo.InvariantCulture)))
                return _Formatter.Format(null, value, null);
//                return string.Format(_Formatter, "{0}", value);

            return base.ConvertTo(context, culture, value, destinationType);
        }

        private PersistentFormatter _Formatter=new PersistentFormatter();
    }

    internal sealed class PersistenceLock:
        IDisposable
    {
        public PersistenceLock()
        {
        }

        ~PersistenceLock()
        {
            _IsDisposed=true;
        }

        public void Dispose()
        {
            _IsDisposed=true;
            GC.SuppressFinalize(this);
        }

        public bool Locked
        {
            get
            {
                return !_IsDisposed;
            }
        }

        private bool _IsDisposed;
    }

}
